package com.sixbro.file.storage.util;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import com.sixbro.file.storage.exception.ApiException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.*;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * <p>
 *
 * </p>
 *
 * @author: Mr.Lu
 * @since: 2022/1/16 14:31
 */
@Slf4j
public class FileUtils extends FileUtil {


    /**
     * 匹配常见图片的base64编码的正则表达式
     */
    private static final Pattern pattern = Pattern.compile("^(data:image/(jpg|jpeg|png);base64,)(\\S+)");

    private final String localFilePath = "";

    private static RestTemplate restTemplate = new RestTemplate();

    private static RestTemplate getRestTemplate() {
        if (Objects.isNull(restTemplate)) {
            restTemplate = new RestTemplate();
        }
        return restTemplate;
    }

    public static void copyInputStreamToFile(InputStream stream, File file) throws IOException {
        if (!file.exists()) {
            file.mkdirs();
        }
        FileOutputStream fos = new FileOutputStream(file);
        int index;
        byte[] bytes = new byte[1024];
        while ((index = stream.read(bytes)) != -1) {
            fos.write(bytes, 0, index);
            fos.flush();
        }
        fos.close();
        stream.close();
    }

    /**
     * MultipartFile转File
     * 选择用缓冲区来实现这个转换即使用java 创建的临时文件 使用 MultipartFile.transferto()方法 。
     */
    public static File multipartFileToFile(MultipartFile multipartFile) {
        // 获取文件名
        String originalFilename = multipartFile.getOriginalFilename();
//        int index = originalFilename.indexOf(".");
//        String prefix = originalFilename.substring(0, index);
//        String suffix = originalFilename.substring(index);
        // 获取文件后缀
        String suffix = "." + getFiletype(originalFilename);
        File file = null;
        try {
            // 用uuid作为文件名，防止生成的临时文件重复
            file = File.createTempFile(IdUtil.simpleUUID(), suffix);
            // MultipartFile to File
            multipartFile.transferTo(file);
            file.deleteOnExit();
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        }
        return file;
    }

    public String fileToBase64(String file) {
        String ext = file.substring(file.lastIndexOf(".") + 1);

        byte[] data = null;
        // 读取图片字节数组
        try {
            //file代表文件的网络路径，若为本地文件，可直接创建new file（file）对象
            URL url = new URL(file);
            URLConnection conn = url.openConnection();
            InputStream in = conn.getInputStream();
            byte[] buffer = new byte[1024 * 1000];
            ByteArrayOutputStream outStream = new ByteArrayOutputStream();
            int len;
            while ((len = in.read(buffer)) != -1) {
                outStream.write(buffer, 0, len);
            }
            data = outStream.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 对字节数组进行Base64编码，得到Base64编码的字符串
        String base64Str = Base64.getEncoder().encodeToString(data);
        base64Str = "data:image/" + ext + ";base64," + base64Str;
        return base64Str;
    }


    public String base64ToFile(String base64Data) {
        String basedir = localFilePath + File.separator + "bbb";

        String filename = "www-" + System.currentTimeMillis();

        boolean result = transferBase64ToFile(base64Data, new File(basedir), filename);
        if (result) {
            return basedir + filename;
        }

        return "";
    }

    /**
     * 将base64编码转成文件保存
     * @param base64Data    完整的base64编码
     * @param destDir       目标目录
     * @return
     */
    public static boolean transferBase64ToFile(String base64Data, File destDir, String filename) {
        Matcher matcher = pattern.matcher(base64Data);
        if (!matcher.find()) {
            return false;
        }

        if (!destDir.exists() && destDir.isDirectory()) {
            destDir.mkdirs();
        }

        // 取出分组信息，必须要在matcher.find()==true之后
        // 取出图片的后缀
        String suffix = matcher.group(2);
        // 取出数据部分
        String data = matcher.group(3);

        File tmp = new File(destDir, filename + "." + suffix);
        byte[] bytes = Base64.getDecoder().decode(data);

//        /*
//         *  方法一：流的拷贝，使用原始的BIO方式
//         */
//        FileOutputStream fos = null;
//        try {
//            fos = new FileOutputStream(tmpFile);
//            fos.write(bytes);
//        } catch (IOException e) {
//            e.printStackTrace();
//            log.error(e.getMessage());
//        } finally {
//            if (fos != null) {
//                try {
//                    fos.close();
//                } catch (IOException e) {
//                    log.error(e.getMessage());
//                }
//            }
//            filename = tmp.getName();
//        }

        /*
         *  方法二：流的拷贝，这里使用NIO的通道相关的API来操作
         */
        // 流的拷贝，可以使用原始的bio方式。在这里使用nio的通道相关的API来操作
        ReadableByteChannel inChannel = Channels.newChannel(new ByteArrayInputStream(bytes));
        FileChannel outChannel = null;
        try {
            outChannel = new FileOutputStream(tmp, true).getChannel();
            outChannel.transferFrom(inChannel, outChannel.size(), bytes.length);
            return true;
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        } finally {
            try {
                inChannel.close();
                outChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            filename = tmp.getName();
        }

    }

    /**
     * 将bas364编码转成byte[]
     * @param base64Data
     * @return
     */
    public static byte[] base64ToBytes(String base64Data) {
        Matcher matcher = pattern.matcher(base64Data);
        if (!matcher.find()) {
            return null;
        }
        // 取出data部分
        String data = matcher.group(3);
        return Base64.getDecoder().decode(data);
    }

    public static void download(String filePath, String fileName, Boolean delete, HttpServletResponse response) throws Exception {
        File file = new File(filePath);
        if (!file.exists()) {
            throw new Exception("文件未找到");
        }

        String fileType = getFiletype(file);
        /**
         * 允许的文件类型，可根据需求添加
         */
        if (!filetypeIsValid(fileType, new String[]{"xlsx", "zip"})) {
            throw new Exception("暂不支持该类型文件下载");
        }
        response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;fileName=" + java.net.URLEncoder.encode(fileName, "utf-8"));
        response.setContentType(MediaType.MULTIPART_FORM_DATA_VALUE);
        response.setCharacterEncoding("utf-8");
        try (InputStream inputStream = new FileInputStream(file); OutputStream os = response.getOutputStream()) {
            byte[] b = new byte[2048];
            int length;
            while ((length = inputStream.read(b)) > 0) {
                os.write(b, 0, length);
            }
        } finally {
            if (delete) {
                delete(filePath);
            }
        }
    }

    public static void download(String url, String storagePath) {
        try {
            List<MediaType> mediaTypes = CollectionUtil.newArrayList(MediaType.APPLICATION_OCTET_STREAM);
            HttpEntity<byte[]> httpEntity = new HttpEntity(mediaTypes);
            ResponseEntity<byte[]> exchange = getRestTemplate().exchange(url, HttpMethod.GET, httpEntity, byte[].class);
            HttpStatus httpStatus = exchange.getStatusCode();
            if (httpStatus.is2xxSuccessful()) {
                FileCopyUtils.copy(Objects.requireNonNull(exchange.getBody()), new File(storagePath));
            }
        } catch (IOException e) {
            throw new ApiException("文件下载失败");
        }
    }

    /**
     * 递归删除文件或目录
     *
     * @param filePath 文件或目录
     */
    public static void delete(String filePath) {
        File file = new File(filePath);
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            if (files != null) {
                Arrays.stream(files).forEach(f -> delete(f.getPath()));
            }
        }
        file.delete();
    }

    public static ResponseEntity<FileSystemResource> export(File file) {
        if (file == null) {
            return null;
        }
        HttpHeaders headers = new HttpHeaders();
        headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
        headers.add("Content-Disposition", "attachment; filename=\"" + file.getName() + "\"");
        headers.add("Pragma", "no-cache");
        headers.add("Expires", "0");
        return ResponseEntity
                .ok()
                .headers(headers)
                .contentLength(file.length())
                .contentType(MediaType.parseMediaType("application/octet-stream"))
                .body(new FileSystemResource(file));
    }

    /**
     * 获取文件类型
     * <p>
     * 例如: ruoyi.txt, 返回: txt
     *
     * @param file 文件名
     * @return 后缀（不含".")
     */
    public static String getFiletype(File file) {
        if (null == file) {
            return "";
        }
        return getFiletype(file.getName());
    }

    /**
     * 获取文件类型
     * <p>
     * 例如: ruoyi.txt, 返回: txt
     *
     * @param filename 文件名
     * @return 后缀（不含".")
     */
    public static String getFiletype(String filename) {
        int separatorIndex = filename.lastIndexOf(".");
        if (separatorIndex < 0) {
            return "";
        }
        return filename.substring(separatorIndex + 1).toLowerCase();
    }

    /**
     * 校验文件类型是否是允许下载的类型
     *
     * @param fileType fileType
     * @return Boolean
     */
    public static Boolean filetypeIsValid(String fileType, String[] fileTypes) {
        Assert.notNull(fileType);
        fileType = fileType.toLowerCase();
        return Arrays.asList(fileTypes).contains(fileType);
    }
}
